const std = @import("std");
const port = @import("port");
const Io = std.Io;
const net = std.Io.net;
const posix = std.posix;
const eql = std.mem.eql;
const print = std.debug.print;

const BUF_SIZE = 4096;

const usage =
    \\Usage: {[0]s} txone
    \\       {[0]s} rxone
    \\       {[0]s} rx
    \\       {[0]s} rx2
    \\       {[0]s} rxburst
    \\       {[0]s} tx
    \\       {[0]s} ping
    \\
;

pub fn main() !void {
    var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init;
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    var threaded: std.Io.Threaded = .init(allocator);
    defer threaded.deinit();
    const io = threaded.io();

    const args = try std.process.argsAlloc(allocator);
    defer std.process.argsFree(allocator, args);

    if (args.len < 2) {
        print(usage, .{std.fs.path.basename(args[0])});
        return;
    }

    if (eql(u8, args[1], "txone")) {
        try txone(io);
    } else if (eql(u8, args[1], "rxone")) {
        try rxone(io);
    }
}

/// listen for a single UDP packet sent by xv6's nettest txone.
/// this tool must be started before xv6's nettest txone.
fn txone(io: Io) !void {
    const server: net.IpAddress = try .parse("127.0.0.1", port.server);
    const socket = try server.bind(io, .{ .mode = .dgram });
    defer socket.close(io);

    print("listening for a UDP packet\n", .{});

    var msg_buffer: [BUF_SIZE]u8 = undefined;
    const msg = try socket.receive(io, &msg_buffer);
    const data = msg.data;

    if (eql(u8, data, "txone")) {
        print("txone: OK\n", .{});
    } else {
        print("txone: unexpected payload {s}\n", .{data});
    }
}

/// send a single UDP packet to xv6 to test e1000.recv().
fn rxone(io: Io) !void {
    const server: net.IpAddress = try .parse("127.0.0.1", port.fwd1);
    const stream = try server.connect(io, .{ .mode = .dgram });
    defer stream.close(io);

    print("sending one UDP packet\n", .{});
    var writer = stream.writer(io, &.{});
    try writer.interface.writeAll("xyz");
}
